#ifndef SOCKET_HPP
#define SOCKET_HPP

#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#endif

#ifdef _WIN32
#include <winsock2.h>  
#pragma comment(lib,"ws2_32.lib")  
typedef int socklen_t;
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> 
#include <unistd.h>
typedef int SOCKET;
#define closesocket(socketfd) close(socketfd)
#define INVALID_SOCKET -1
#endif
#include <string>
#include <string.h>

class Socket
{
public:
	int ip_port;
	std::string ip_addr;
	SOCKET sock;
	std::string socket_name;
	sockaddr_in socket_addr;
	std::string protocol;
	const int send_buf_size = 512 * 1024;
	const int rcv_buf_size = 512 * 1024;

	Socket(SOCKET s, sockaddr_in so_addr, std::string proto)
	{
		char *p = inet_ntoa(so_addr.sin_addr);
		ip_addr = std::string(p);
		ip_port = ntohs(so_addr.sin_port);
		sock = s;
		protocol = proto;
		socket_name = std::string(ip_addr) + ":" + std::to_string(ip_port);
		memcpy(&socket_addr, &so_addr, sizeof(sockaddr_in));
	}

	Socket()
	{
	}

	void Copyto(Socket& s)const
	{
		s.ip_port = ip_port;
		s.ip_addr = ip_addr;
		s.sock = sock;
		s.socket_name = socket_name;
		s.protocol = protocol;
		memcpy(&s.socket_addr, &socket_addr, sizeof(sockaddr_in));
	}

	Socket(const Socket& s)
	{
		s.Copyto(*this);
	}

	Socket operator=(const Socket& s)
	{
		s.Copyto(*this);
		return *this;
	}

	Socket(const std::string IP, const int port, std::string proto)
	{
		ip_addr = IP;
		ip_port = port;
		socket_name = IP + ":" + std::to_string(port);
		protocol = proto;
		memset(&socket_addr, 0, sizeof(socket_addr));
		socket_addr.sin_family = AF_INET;
		socket_addr.sin_addr.s_addr = inet_addr(IP.c_str());
		socket_addr.sin_port = htons(ip_port);
		

		if (protocol == "TCP")
		{
			sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		}
		else if (protocol == "UDP")
		{
			sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
		}		

		if (sock == INVALID_SOCKET)
		{
			throw "Socket error";
			return;
		}
		
		int ret;

		ret = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (const char*)&send_buf_size, sizeof(send_buf_size));
		
		if (ret < 0)
		{
			throw "Set Socket Error";
			return;
		}

		ret = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (const char*)&rcv_buf_size, sizeof(rcv_buf_size));

		if (ret < 0)
		{
			throw "Set Socket Error";
			return;
		}
	}


	void Close()
	{
		closesocket(sock);
	}

	int Acceptfrom(Socket & client)
	{
		int nAddrlen = sizeof(sockaddr);
		client.sock = accept(this->sock, (sockaddr*)&client.socket_addr, (socklen_t*)&nAddrlen);
		if (client.sock == INVALID_SOCKET)
		{
			printf("%s Accept Error\n", socket_name.c_str());
			return -1;
		}

		char* p = inet_ntoa(client.socket_addr.sin_addr);
		client.ip_addr = std::string(p);
		client.ip_port = ntohs(client.socket_addr.sin_port);
		client.protocol = this->protocol;
		client.socket_name = std::string(client.ip_addr) + ":" + std::to_string(client.ip_port);

		printf("%s accepts %s's connection\n", socket_name.c_str(),client.socket_name.c_str());
		return 0;
	}

	int Bind()
	{
		//ȫ�ֺ���
		if (::bind(sock, (sockaddr*)&socket_addr, sizeof(socket_addr)) < 0)
		{
			printf("%s Bind Error\n", socket_name.c_str());
			return -1;
		}
		printf("%s Bind succeed\n", socket_name.c_str());
		return 0;
	}

	int Listen(int n)
	{
		if (::listen(sock, n) < 0)
		{
			printf("%s Listen Error\n", socket_name.c_str());
			return -1;
		}
		printf("%s waits for connecting \n", socket_name.c_str());
		return 0;
	}
	
	friend int Connectto(Socket & s);
	friend int Sendto(const Socket& s, const char* sendData, int len);
	friend int Recvfrom(const Socket& s, char* recvData, int maxlen);
	
	~Socket()
	{
		Close();
	}
	
};


int Connectto(Socket & s);
int Sendto(const Socket& s, const char* sendData, int len);
int Recvfrom(const Socket& s, char* recvData, int maxlen);
#endif